package GMDPRE

func DoubleJacP2JacP(r *JacPoint, jp *JacPoint) {
	var tmp1 BN
	var tmp2 BN
	var lambda1 BN
	var lambda2 BN
	var lambda3 BN

	if BnIsZero(&(jp.z), Sm2CurveDigit) == 1 {
		*r = *jp
		return
	}

	/* λ1 = 3X^2 + aZ^4 = 3X^2 - 3Z^4 = 3 * ((X + Z^2) * (X - Z^2)) */
	/* tmp2 = z^2 */
	BnMontMul(&tmp2, &(jp.z), &(jp.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp1 = x + z^2 */
	BnModAdd(&tmp1, &(jp.x), &tmp2, &Sm2Q, Sm2CurveDigit)
	/* tmp2 = x - z^2 */
	BnModSub(&tmp2, &(jp.x), &tmp2, &Sm2Q, Sm2CurveDigit)
	/* lambda1 = (x + z^2)*(x - z^2)  */
	BnMontMul(&lambda1, &tmp1, &tmp2, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp1 = 2*lambda1 */
	BnModAdd(&tmp1, &lambda1, &lambda1, &Sm2Q, Sm2CurveDigit)
	/* lambda1 = 3*(x + z^2)*(x - z^2) */
	BnModAdd(&lambda1, &tmp1, &lambda1, &Sm2Q, Sm2CurveDigit)

	/* λ2 = 4XY^2 = X * (2Y)^2 */
	/* tmp1 = 2Y */
	BnModAdd(&tmp1, &(jp.y), &(jp.y), &Sm2Q, Sm2CurveDigit)
	/* Z3 = 2YZ */
	BnMontMul(&(r.z), &tmp1, &(jp.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* tmp2 = (2Y)^2 */
	BnMontMul(&tmp2, &tmp1, &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* lambda2 = X * (2Y)^2 */
	BnMontMul(&lambda2, &(jp.x), &tmp2, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* λ3 = 8y^4 = 2 * (2 * Y^2) ^ 2 */
	/* tmp1 = y^2 */
	BnMontMul(&tmp1, &(jp.y), &(jp.y), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp2 = 2*y^2 */
	BnModAdd(&tmp2, &tmp1, &tmp1, &Sm2Q, Sm2CurveDigit)
	/* tmp1 = (2*y^2)^2 */
	BnMontMul(&tmp1, &tmp2, &tmp2, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* lambda3 = 2*tmp1 = 2*(2*y^2)^2*/
	BnModAdd(&lambda3, &tmp1, &tmp1, &Sm2Q, Sm2CurveDigit)

	/* X3 = λ1^2 − 2λ2 */
	/* tmp1 = λ1^2 */
	BnMontMul(&tmp1, &lambda1, &lambda1, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp2 = 2λ2 */
	BnModAdd(&tmp2, &lambda2, &lambda2, &Sm2Q, Sm2CurveDigit)
	/* r->x = tmp1-tmp2 */
	BnModSub(&(r.x), &tmp1, &tmp2, &Sm2Q, Sm2CurveDigit)

	/* Y3 = λ1(λ2 − X3) − λ3 */
	/* tmp1 = λ2 − X3 */
	BnModSub(&tmp1, &lambda2, &(r.x), &Sm2Q, Sm2CurveDigit)
	/* tmp1 = λ1(λ2 − X3) */
	BnMontMul(&tmp1, &lambda1, &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* r->y = tmp1 - lambda3 */
	BnModSub(&(r.y), &tmp1, &lambda3, &Sm2Q, Sm2CurveDigit)
}

func JacPAddJacP2JacP(r *JacPoint, jp1 *JacPoint, jp2 *JacPoint) {
	var tmp1, tmp2 BN
	var u1, u2 BN
	var s1, s2 BN
	var hh, rr BN

	if BnIsZero(&(jp1.z), Sm2CurveDigit) == 1 {
		*r = *jp2
		return
	}
	if BnIsZero(&(jp2.z), Sm2CurveDigit) == 1 {
		*r = *jp1
		return
	}

	if BnCmp(&(jp1.x), &(jp2.x), Sm2CurveDigit) == 0 &&
		BnCmp(&(jp1.y), &(jp2.y), Sm2CurveDigit) == 0 &&
		BnCmp(&(jp1.z), &(jp2.z), Sm2CurveDigit) == 0 {
		DoubleJacP2JacP(r, jp1)
		return
	}
	/* tmp1 = z1 ^ 2 */
	BnMontMul(&tmp1, &(jp1.z), &(jp1.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp2 = z2 ^ 2 */
	BnMontMul(&tmp2, &(jp2.z), &(jp2.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* u1 = x1 * z2 ^ 2 */
	BnMontMul(&u1, &(jp1.x), &tmp2, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* u2 = x2 * z1 ^ 2 */
	BnMontMul(&u2, &(jp2.x), &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* s1 = y1 * z2 ^ 3*/
	BnMontMul(&s2, &tmp2, &(jp2.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	BnMontMul(&s1, &(jp1.y), &s2, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* s2 = y2 * z1 ^ 3*/
	BnMontMul(&s2, &tmp1, &(jp1.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	BnMontMul(&s2, &(jp2.y), &s2, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* H = u2 - u1 */
	BnModSub(&hh, &u2, &u1, &Sm2Q, Sm2CurveDigit)

	/* R = s2 - s1 */
	BnModSub(&rr, &s2, &s1, &Sm2Q, Sm2CurveDigit)

	/* tmp1 = H^2 */
	BnMontMul(&tmp1, &hh, &hh, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp2 = U1 * H^2 */
	BnMontMul(&tmp2, &u1, &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp1 = H^3 */
	BnMontMul(&tmp1, &hh, &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)

	/* r->x = R^2 - H^3 - 2 * U1 * H^2 */
	BnMontMul(&(r.x), &rr, &rr, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	BnModSub(&(r.x), &(r.x), &tmp1, &Sm2Q, Sm2CurveDigit)
	BnModSub(&(r.x), &(r.x), &tmp2, &Sm2Q, Sm2CurveDigit)
	BnModSub(&(r.x), &(r.x), &tmp2, &Sm2Q, Sm2CurveDigit)

	/* r->y = R * (U1 * H^2 - X3) - S1 * H^3 */
	/* tmp2 = tmp2 - r->x = U1 * H^2 - X3 */
	BnModSub(&tmp2, &tmp2, &(r.x), &Sm2Q, Sm2CurveDigit)
	/* r->y = R * tmp2 = R(U1 * H^2 - X3) */
	BnMontMul(&(r.y), &rr, &tmp2, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* tmp1 = s1 * tmp1 = S1 * H^3 */
	BnMontMul(&tmp1, &s1, &tmp1, &Sm2Q, Sm2McQ, Sm2CurveDigit)
	/* r->y = r->y - tmp1 = R(U1 * H^2 - X3) - S1 * H^3 */
	BnModSub(&(r.y), &(r.y), &tmp1, &Sm2Q, Sm2CurveDigit)

	/* r->z = H * Z1 * Z2 */
	BnMontMul(&tmp1, &hh, &(jp1.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
	BnMontMul(&(r.z), &tmp1, &(jp2.z), &Sm2Q, Sm2McQ, Sm2CurveDigit)
}

func JacPAddJacP2AffP(r *AffPoint, jp1 *JacPoint, jp2 *JacPoint) {
	var tmp JacPoint
	JacPAddJacP2JacP(&tmp, jp1, jp2)
	JacP2AffC(&r.x, &r.y, &tmp)
}

func NumMulJacP2JacP(r *JacPoint, d *BN, jp *JacPoint) {
	var i, j, k int

	var bits = BnGetBits(d, Sm2CurveDigit)
	var ans = JacPoint{BN{1}, BN{1}, BN{0}}
	var base = *jp

	for i = 0; i < bits; i++ {
		j = i >> 5
		k = i & 0b11111
		if (d[j]>>k)&0x1 > 0 {
			JacPAddJacP2JacP(&ans, &ans, &base)
		}
		DoubleJacP2JacP(&base, &base)
	}
	*r = ans
}

func NumMulAffP2JacP(res *JacPoint, d *BN, ap *AffPoint) {
	var tmp JacPoint
	AffP2JacP(&tmp, ap)
	NumMulJacP2JacP(res, d, &tmp)
}

func NumMulJacP2AffP(ap *AffPoint, d *BN, jp *JacPoint) {
	var ans = JacPoint{BN{1}, BN{1}, BN{0}}
	NumMulJacP2JacP(&ans, d, jp)
	JacP2AffC(&ap.x, &ap.y, &ans)
}

// NumMulAffP2AffP computes r = [d]p
func NumMulAffP2AffP(r *AffPoint, d *BN, p *AffPoint) {
	var tmp JacPoint
	AffP2JacP(&tmp, p)
	NumMulJacP2AffP(r, d, &tmp)
}

// NumMulG2AffC computes (x2, y2) = [d]G
func NumMulG2AffC(x2 *BN, y2 *BN, d *BN) {
	var ans JacPoint
	NumMulJacP2JacP(&ans, d, &Sm2JacG)
	JacP2AffC(x2, y2, &ans)
}

// NumMulG2AffP computes P = [d]G, P as Aff Point
func NumMulG2AffP(ap *AffPoint, d *BN) {
	var ans JacPoint
	NumMulJacP2JacP(&ans, d, &Sm2JacG)
	JacP2AffC(&ap.x, &ap.y, &ans)
}
